package com.grandtech.insurance.common.db.mybites.intercepts;

import com.grandtech.insurance.common.db.jdbc.sql.ReflectUtil;
import com.grandtech.insurance.common.db.jdbc.sql.TransferUtil;
import com.grandtech.insurance.common.db.mybites.utils.InterceptUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Properties;


@Intercepts({
        @Signature(type = Executor.class,
                method = "update",
                args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
                        CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MbPreIntercept implements Interceptor {
    // 这是对应上面的args的序号
    static int MAPPED_STATEMENT_INDEX = 0;
    static int PARAMETER_INDEX = 1;
    static int ROWBOUNDS_INDEX = 2;
    static int RESULT_HANDLER_INDEX = 3;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return proxy(invocation);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * sql查询代理
     *
     * @param invocation
     */
    public Object proxy(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        Object[] queryArgs = invocation.getArgs();
        MappedStatement oldMappedStatement = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];

        Object parameterObject = null;
        BoundSql boundSql = null;
        Object result = null;
        if (invocation.getArgs().length > 1) {
            parameterObject = queryArgs[PARAMETER_INDEX];
            boundSql = oldMappedStatement.getBoundSql(parameterObject);
        }
        SqlBean sqlBean = buildSql(oldMappedStatement, parameterObject);
        Method method = invocation.getMethod();
        if (target instanceof Executor) {
            if (sqlBean != null && sqlBean.isReBuild) {
                BoundSql newBoundSql = new BoundSql(oldMappedStatement.getConfiguration(), sqlBean.sql, null, null);
                MappedStatement newMappedStatement = InterceptUtil.copyFromMappedStatement(oldMappedStatement, new InterceptUtil.BoundSqlSqlSource(newBoundSql));
                for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                    String prop = mapping.getProperty();
                    if (boundSql.hasAdditionalParameter(prop)) {
                        newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                    }
                }
                queryArgs[MAPPED_STATEMENT_INDEX] = newMappedStatement;
            }
        }
        /**执行方法*/
        long start = System.currentTimeMillis();
        result = invocation.proceed();
        long end = System.currentTimeMillis();
        if(null==sqlBean){
            System.out.println("[mybites] 执行 [" + method.getName() + "] 耗时 [" + (end - start) + "] ms + sql [  ]");
        }else {
            System.out.println("[mybites] 执行 [" + method.getName() + "] 耗时 [" + (end - start) + "] ms + sql [ " + sqlBean.toString() + " ]");
        }
        return result;
    }

    /**
     * @param mappedStatement
     * @param parameterObject
     * @return
     */
    private SqlBean buildSql(MappedStatement mappedStatement, Object parameterObject) {
        boolean isSqlRebuild = false;
        if (mappedStatement == null || parameterObject == null) return null;
        Configuration configuration = mappedStatement.getConfiguration();
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        if (boundSql == null) return null;
        String sql = boundSql.getSql();
        List<ParameterMapping> mappings = boundSql.getParameterMappings();
        Object object = boundSql.getParameterObject();
        if (sql == null || object == null) return null;
        if (mappings.size() <= 0 || parameterObject == null) return new SqlBean(sql, false);
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        Object obj;
        String sqlItem;
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            sqlItem = TransferUtil.object2SqlString(parameterObject);
            sql = sql.replaceFirst("\\?", sqlItem);
        } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping mapping : mappings) {
                String propertyName = mapping.getProperty();
                if (metaObject.hasGetter(propertyName)) {
                    obj = metaObject.getValue(propertyName);

                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    obj = boundSql.getAdditionalParameter(propertyName);

                } else if (ReflectUtil.hasAnnotation(parameterObject, propertyName)) {
                    obj = ReflectUtil.getFieldByAnnotation(parameterObject, propertyName);
                    isSqlRebuild = true;
                } else {
                    continue;
                }
                sqlItem = TransferUtil.object2SqlString(obj);
                if(sqlItem ==null){
                   sqlItem="null" ;
                }
                sql = sql.replaceFirst("\\?", sqlItem);
            }
        }
        SqlBean sqlBean = new SqlBean(sql, isSqlRebuild);
        return sqlBean;
    }

    class SqlBean {
        private Boolean isReBuild = false;

        private String sql;

        public SqlBean(String sql, Boolean isReBuild) {
            this.sql = sql;
            this.isReBuild = isReBuild;
        }

        public Boolean getReBuild() {
            return isReBuild;
        }

        public void setReBuild(Boolean reBuild) {
            isReBuild = reBuild;
        }

        public String getSql() {
            return sql;
        }

        public void setSql(String sql) {
            this.sql = sql;
        }

        @Override
        public String toString() {
            return "SqlBean{" +
                    "isReBuild=" + isReBuild +
                    ", sql='" + sql + '\'' +
                    '}';
        }
    }
}
